package com.stars.easyms.base.listener;

import com.stars.easyms.base.event.EasyMsConfigChange;
import com.stars.easyms.base.event.EasyMsConfigChangeEvent;
import com.stars.easyms.base.event.EasyMsConfigChangeType;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.*;
import org.springframework.web.context.support.StandardServletEnvironment;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>className: EasyMsConfigChangeApplicationListener</p>
 * <p>description: EasyMs的配置刷新的application监听器</p>
 *
 * @author guoguifang
 * @version 1.7.0
 * @date 2020/11/25 11:31 上午
 */
public class EasyMsConfigChangeApplicationListener implements ApplicationListener<EnvironmentChangeEvent>, Ordered {

    private final MutablePropertySources propertySources;

    private MutablePropertySources fixedPropertySources;

    private Set<String> standardSources = new HashSet<>(
            Arrays.asList(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,
                    StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,
                    StandardServletEnvironment.JNDI_PROPERTY_SOURCE_NAME,
                    StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME,
                    StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME,
                    "configurationProperties"));

    public EasyMsConfigChangeApplicationListener(ApplicationContext applicationContext) {
        this.propertySources = ((ConfigurableEnvironment) applicationContext.getEnvironment()).getPropertySources();
        this.fixPropertySources();
    }

    @Override
    public void onApplicationEvent(EnvironmentChangeEvent event) {
        // 获取到所有更新的key值
        Set<String> refreshKeys = event.getKeys();
        if (refreshKeys.isEmpty()) {
            return;
        }

        // 获取到所有的刷新的值
        Map<String, Object> oldRefreshMap = extract(fixedPropertySources, refreshKeys);
        Map<String, Object> newRefreshMap = extract(propertySources, refreshKeys);

        // 重新固化PropertySources
        fixPropertySources();

        // 整合获取配置修改事件对象
        Map<String, EasyMsConfigChange> configChangeMap = new HashMap<>(refreshKeys.size());
        for (String refreshKey : refreshKeys) {
            // 获取旧值与新值
            Object oldValue = oldRefreshMap.get(refreshKey);
            Object newValue = newRefreshMap.get(refreshKey);
            if (!Objects.equals(oldValue, newValue)) {
                // 获取配置修改类型
                EasyMsConfigChangeType changeType = EasyMsConfigChangeType.MODIFIED;
                if (oldValue == null) {
                    changeType = EasyMsConfigChangeType.ADDED;
                } else if (newValue == null) {
                    changeType = EasyMsConfigChangeType.DELETED;
                }
                EasyMsConfigChange configChange = new EasyMsConfigChange(refreshKey, oldValue, newValue, changeType);
                configChangeMap.put(refreshKey, configChange);
            }
        }
        if (configChangeMap.isEmpty()) {
            return;
        }

        // 处理已经注册的配置修改事件类
        EasyMsConfigChangeListenerRegister.getConfigRefreshListenerList().forEach(l -> {
            String listenConfigPrefix = l.listenConfigPrefix();
            if (listenConfigPrefix == null) {
                l.listen(new EasyMsConfigChangeEvent(configChangeMap));
                return;
            }
            Map<String, EasyMsConfigChange> m = configChangeMap.keySet()
                    .stream()
                    .filter(s -> s.startsWith(listenConfigPrefix))
                    .collect(Collectors.toMap(s -> s, configChangeMap::get));
            if (!m.isEmpty()) {
                l.listen(new EasyMsConfigChangeEvent(m));
            }
        });
    }

    @Override
    public int getOrder() {
        return LOWEST_PRECEDENCE;
    }

    private Map<String, Object> extract(MutablePropertySources propertySources, Set<String> refreshKeys) {
        Map<String, Object> result = new HashMap<>(32);
        List<PropertySource<?>> sources = new ArrayList<>();
        for (PropertySource<?> source : propertySources) {
            sources.add(0, source);
        }
        for (PropertySource<?> source : sources) {
            if (!this.standardSources.contains(source.getName())) {
                extract(source, refreshKeys, result);
            }
        }
        return result;
    }

    private void extract(PropertySource<?> parent, Set<String> refreshKeys, Map<String, Object> result) {
        if (parent instanceof CompositePropertySource) {
            try {
                List<PropertySource<?>> sources = new ArrayList<>();
                for (PropertySource<?> source : ((CompositePropertySource) parent)
                        .getPropertySources()) {
                    sources.add(0, source);
                }
                for (PropertySource<?> source : sources) {
                    extract(source, refreshKeys, result);
                }
            } catch (Exception e) {
                // ignore
            }
        } else if (parent instanceof EnumerablePropertySource) {
            for (String key : ((EnumerablePropertySource<?>) parent).getPropertyNames()) {
                if (refreshKeys.contains(key)) {
                    result.put(key, parent.getProperty(key));
                }
            }
        }
    }

    private void fixPropertySources() {
        this.fixedPropertySources = new MutablePropertySources(propertySources);
    }
}
